﻿using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using NewLife;
using NewLife.Log;
using NewLife.Web;

namespace XCoder
{
    /// <summary>GTK助手，自动检查安装GTK运行时</summary>
    public class GtkHelper
    {
        #region 属性
        public String GtkRoot { get; set; }

        public String GtkPath { get; set; }

        public Version Version { get; set; }
        #endregion

        #region 主要方法
        public static Task CheckRuntime(Int32 msTimeout = 3_000)
        {
            var task = Task.Run(async () =>
            {
                var gtk = new GtkHelper { Log = XTrace.Log };
                if (!gtk.Check()) await gtk.DownloadAsync();

                gtk.Install();
            });
            // 最多等3秒
            task.Wait(msTimeout);

            return task;
        }

        /// <summary>检查是否安装有GTK运行时</summary>
        /// <returns></returns>
        public Boolean Check()
        {
            // 只处理Windows
            if (!Runtime.Windows) return true;

            // LOCALDATA
            if (GtkRoot.IsNullOrEmpty())
            {
                var data = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
                GtkRoot = data.CombinePath("Gtk").GetFullPath();
            }
            //XTrace.WriteLine("查找GTK运行时：{0}", GtkRoot);

            var di = GtkRoot.AsDirectory();
            if (!di.Exists) return false;

            var dis = di.GetDirectories();
            if (dis == null || dis.Length == 0) return false;

            //var gtk = dis.OrderByDescending(e => e.Name).FirstOrDefault();
            //GtkPath = gtk.FullName;
            //Version = new Version(gtk.Name.TrimStart('v', 'V'));
            foreach (var item in dis)
            {
                var gtk = item.FullName.CombinePath("libgdk-3-0.dll");
                if (File.Exists(gtk))
                {
                    GtkPath = item.FullName;

                    try
                    {
                        Version = new Version(item.Name.TrimStart('v', 'V'));
                    }
                    catch { }

                    break;
                }
            }

            if (GtkPath.IsNullOrEmpty()) return false;

            XTrace.WriteLine("发现GTK运行时：[{0}] {1}", Version, GtkPath);

            return true;
        }

        /// <summary>下载</summary>
        /// <returns></returns>
        public async Task<Boolean> DownloadAsync()
        {
            var set = NewLife.Setting.Current;

            //var client = new WebClientX
            //{
            //    Log = XTrace.Log
            //};

            //var links = client.GetLinks(set.PluginServer);
            //var lnk = links.First(e => e.Name.EqualIgnoreCase("Gtk"));

            //var file = client.DownloadLink(set.PluginServer, "Gtk", GtkRoot);
            var file = await DownloadLinkAsync(set.PluginServer, "Gtk", GtkRoot);
            var link = new Link();
            link.Parse(file);

            XTrace.WriteLine("版本：{0}", link.Version);

            //client.DownloadLinkAndExtract(set.PluginServer, "Gtk", gtk, true);

            GtkPath = GtkRoot.CombinePath(link.Version + "");
            Version = link.Version;

            Extract(file, GtkPath, true);

            return true;
        }

        /// <summary>安装</summary>
        public void Install()
        {
            if (!GtkPath.IsNullOrEmpty()) SetDllDirectory(GtkPath);
        }
        #endregion

        #region 辅助
        /// <summary>分析指定页面指定名称的链接，并下载到目标目录，返回目标文件</summary>
        /// <remarks>
        /// 根据版本或时间降序排序选择
        /// </remarks>
        /// <param name="urls">指定页面</param>
        /// <param name="name">页面上指定名称的链接</param>
        /// <param name="destdir">要下载到的目标目录</param>
        /// <returns>返回已下载的文件，无效时返回空</returns>
        private async Task<String> DownloadLinkAsync(String urls, String name, String destdir)
        {
            Log.Info("下载链接 {0}，目标 {1}", urls, name);

            var names = name.Split(",", ";");

            var file = "";
            Link link = null;
            Exception lastError = null;
            foreach (var url in urls.Split(",", ";"))
            {
                try
                {
                    var http = new HttpClient();
                    var html = await http.GetStringAsync(url);
                    var ls = Link.Parse(html, url);
                    if (ls.Length == 0) return file;

                    // 过滤名称后降序排序，多名称时，先确保前面的存在，即使后面名称也存在并且也时间更新都不能用
                    foreach (var item in names)
                    {
                        link = ls.Where(e => !e.Url.IsNullOrWhiteSpace())
                           .Where(e => e.Name.EqualIgnoreCase(item) || e.FullName.Equals(item))
                           .OrderByDescending(e => e.Version)
                           .ThenByDescending(e => e.Time)
                           .FirstOrDefault();
                        if (link != null) break;
                    }
                }
                catch (Exception ex)
                {
                    lastError = ex;
                }
                if (link != null) break;
            }
            if (link == null)
            {
                if (lastError != null) throw lastError;

                return file;
            }

            var linkName = link.FullName;
            var file2 = destdir.CombinePath(linkName).EnsureDirectory();

            // 已经提前检查过，这里几乎不可能有文件存在
            if (File.Exists(file2))
            {
                // 如果连接名所表示的文件存在，并且带有时间，那么就智能是它啦
                var p = linkName.LastIndexOf("_");
                if (p > 0 && (p + 8 + 1 == linkName.Length || p + 14 + 1 == linkName.Length))
                {
                    Log.Info("分析得到文件 {0}，目标文件已存在，无需下载 {1}", linkName, link.Url);
                    return file2;
                }
            }

            Log.Info("分析得到文件 {0}，准备下载 {1}，保存到 {2}", linkName, link.Url, file2);
            // 开始下载文件，注意要提前建立目录，否则会报错
            file2 = file2.EnsureDirectory(true);

            var sw = Stopwatch.StartNew();
            {
                var http = new HttpClient();
                var ns = await http.GetStreamAsync(link.Url);
                using var fs = File.OpenWrite(file2);
                await ns.CopyToAsync(fs);
            }
            //TaskEx.Run(() => DownloadFileAsync(link.Url, file2)).Wait();
            sw.Stop();

            if (File.Exists(file2))
            {
                Log.Info("下载完成，共{0:n0}字节，耗时{1:n0}毫秒", file2.AsFile().Length, sw.ElapsedMilliseconds);
                file = file2;
            }

            return file;
        }

        /// <summary>分析指定页面指定名称的链接，并下载到目标目录，解压Zip后返回目标文件</summary>
        /// <param name="urls">提供下载地址的多个目标页面</param>
        /// <param name="name">页面上指定名称的链接</param>
        /// <param name="destdir">要下载到的目标目录</param>
        /// <param name="overwrite">是否覆盖目标同名文件</param>
        /// <returns></returns>
        private String Extract(String file, String destdir, Boolean overwrite = false)
        {
            if (file.IsNullOrEmpty()) return null;

            // 解压缩
            try
            {
                Log.Info("解压缩到 {0}", destdir);
                //ZipFile.ExtractToDirectory(file, destdir);
                file.AsFile().Extract(destdir, overwrite);

                // 删除zip
                File.Delete(file);

                return file;
            }
            catch (Exception ex)
            {
                Log.Error(ex?.GetTrue()?.ToString());

                // 这个时候出现异常，删除zip
                if (!file.IsNullOrEmpty() && File.Exists(file))
                {
                    try
                    {
                        File.Delete(file);
                    }
                    catch { }
                }
            }

            return null;
        }

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
        static extern Int32 SetDllDirectory(String pathName);
        #endregion

        #region 日志
        public ILog Log { get; set; } = Logger.Null;

        public void WriteLog(String format, params Object[] args) => Log?.Info(format, args);
        #endregion
    }
}
